Amazon Timestream のメジャーのクォータを確認してみた
今回は、Amazon Timestream を使うときに抑えておきたいクォータ(制限値)について確認してみました。結論は下記の図のとおりです。
(誤り等ありましたらご指摘いただけると幸いです)
上記の図がこの記事で伝えたいことの全てになります。それぞれの詳細については、お時間あるときにでも下に書いている検証内容をご覧いただければ幸いです。
なお、他にもクォータは存在するので、全てを確認する場合は公式ドキュメントよりご確認ください。
Amazon Timestream のクォータ(制限値)について
どんなサービスにもクォータ(制限値)は存在しており、利用する上でクォータを確認しておくことは大切です。
下記のページにあるように、Amazon Timestream にもクォータは存在しますが、全てハードリミットになっており上限緩和を申請して制限値を増やすことができません。(2022年2月9日現在)
ドキュメントに上限緩和の有無は記載されていませんが、AWS Service Quotas の画面から確認すると、Amazon Timestream の全ての項目で緩和できないことが分かります。
また、AWS サポートのサポートケースでも上限緩和用のサービスを選択するプルダウンに Amazon Timestream は存在しませんでした。
そのため、Amazon Timestream を使う場合は、これらの各クォータの意味を正確に把握した上で要件に合致しているかどうか確認する必要があります。
特にマルチメジャーレコードを使う場合は、センサーなどから送られてくるデータの項目数が多いと思わぬエラーに遭遇することになります。
シングルメジャーレコードの場合は「1項目のデータに対して1レコード」であり、書き込む際も「Big int、double、string、boolean」のいずれかの項目として書き込まれるのでカラムが 4つ以上に増えることもありません。
そのためシングルメジャーレコードの場合、あまりクォータを意識しなくても使えるケースが多いかと思います。
サンプルスクリプトでクォータを確認する
今回は、今後よく使うことになると思われるマルチメジャーレコードの制限値についてサンプルスクリプトを通じて確認してみました。コードは前回と同じものを使います。
import boto3 from botocore.config import Config import time import random session = boto3.Session() write_client = session.client('timestream-write', region_name='us-east-1', config=Config(read_timeout=20, # リクエストタイムアウト(秒) max_pool_connections=5000, # 最大接続数 retries={'max_attempts': 10})) # 最大試行回数 # Amazon Timestream データベースとテーブル DatabaseName='mytestdb' TableName='mytesttbl' # タイムスタンプの付与 def current_milli_time(): return round(time.time() * 1000) # ディメンションの作成 def gen_dimensions(): municipalities = random.choice(['Shinjuku', 'Toshima', 'Nakano', 'Ota', 'Chiyoda']) gateway_id = random.choice(['gateway_1', 'gateway_2', 'gateway_3']) device_name = str(municipalities) + '_' + str(gateway_id) dimensions = [ {'Name': 'Location', 'Value': 'Tokyo'}, {'Name': 'Municipalities', 'Value': municipalities}, {'Name': 'DeviceName', 'Value': device_name} ] return dimensions # 何件のメジャー値(アイテム、RDBのカラムに相当するもの)を作成するか # 1レコードあたりに入れるアイテムを複数作成する def gen_item(): records = [] MultiMeasureValue = [] start_item_num = 0 # 任意のアイテム番号からMeasureValueを作成 end_item_num = 10 # start_item_numから 256を超えない範囲で指定 for multi_measure_num in range(start_item_num,end_item_num): #ランダムな数値データをメジャー値として作成 MeasureValue = str(random.uniform(1, 90)) dummy_multi_measure = 'item_' + str(multi_measure_num) myitem = { 'Name': dummy_multi_measure, 'Value': MeasureValue, 'Type': 'DOUBLE' }, records.append(myitem) t = records[multi_measure_num - start_item_num] MultiMeasureValue.append(t[0]) # 要素だけ抽出して、それを連結して変数に入れる return MultiMeasureValue # レコードの生成 def gen_dummy_record(): records_X = [] # 何件のレコードを作成するか ; 100件:(0,100) 最大100 for record_num in range(0,1): dummy_measure = { 'Dimensions': gen_dimensions(), 'MeasureName': 'dummy_metrics', 'MeasureValueType': 'MULTI', 'MeasureValues': gen_item(), 'Time': str(current_milli_time()), 'TimeUnit': 'MILLISECONDS' } records_X.append(dummy_measure) time.sleep(1/1000) return records_X for write_num in range(0,1): # 生成したレコードを何回書き込むか print ("start write_records...: " + str(write_num)) result = write_client.write_records(DatabaseName=DatabaseName, TableName=TableName, Records=gen_dummy_record(), CommonAttributes={})
Measures per multi-measure record : 256
の検証
検証の目的
ここでは、「1レコードあたりの書込み可能なバリュー名(データ項目)の最大個数が 256」 であることを確認します。
検証手順
最初に 37 〜 38 行目を start_item_num = 0
,end_item_num = 256
で実行してメジャーバリューが 256 個書き込めることを確認します。
$ python3 write_multi_measure.py
クエリの結果からも 256 個のバリュー値を書き込めていることが分かります。
(item_0
〜item_255
を格納したので、最後のitem_255
が格納されていれば OK とします。)
(なお、SQL でカラム数をカウントしてみた所 information_schema
が予約された名前でありクエリが通りませんでした。)
次に先程変更した 37 〜 38 行目を start_item_num = 0
,end_item_num = 257
で実行してエラーになることを確認します。
$ python3 write_multi_measure.py
下記のようなエラーメッセージが出ます(見やすさのため適宜改行しています)
botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the WriteRecords operation: You have reached the maximum number of Multi measureValue names for a record. See Quotas in the developer guide for additional information.
これにより、マルチメジャーレコード 1 行あたり格納できるバリュー名(データ項目)の最大数は 256 個であることが分かりました。
終わったらstart_item_num = 0
,end_item_num = 1
とかに戻しておきます。
Unique measures across multi-measure records per table : 1024
の検証
検証の目的
ここでは「マルチメジャーレコード全体における (measure_name
を除いた)レコードあたりのメジャー(メジャーバリューではない)の最大数が1024」であることを確認します。
検証手順
最初に 37 〜 38 行目を start_item_num = 0
,end_item_num = 256
で実行してメジャーバリュー 256 個を書き込んで、対象テーブルのメジャー数を 256 個にします。
先程検証したように、1 つのマルチメジャーレコードに格納できるメジャー最大数は 256 個なので、256 個ずつデータ項目をずらして書き込んでいきます。そのためコードを都度修正して下記のように書き込みを行います。
start_item_num = 256
,end_item_num = 512
で書き込み- テーブルのメジャー全体を前回との合計で512個にする
start_item_num = 512
,end_item_num = 768
で書き込み- テーブルのメジャー全体を前回との合計で768個にする
start_item_num = 768
,end_item_num = 1024
で書き込み- テーブルのメジャー全体を前回との合計で1024個にする
最後に start_item_num = 1024
,end_item_num = 1025
で実行してエラーになることを確認します。今度は RejectedRecordsException
というエラーが発生しました。
(見やすさのため適宜改行しています)
botocore.errorfactory.RejectedRecordsException: An error occurred (RejectedRecordsException) when calling the WriteRecords operation: One or more records have been rejected. See RejectedRecords for details.
RejectedRecords
の詳細は下記になります。
このドキュメントによると、RejectedRecords
の原因の一つとして、「Timsstream のクォータを超えた 」事が原因と書かれておりクォータに関するページが案内されています。クォータのページを見ると、1024
は Unique measures across multi-measure records per table
の項目のみなので、たしかに「 マルチメジャーレコード全体における レコードあたりのメジャーの最大数は 1024 」であることが分かります。
終わったらstart_item_num = 0
,end_item_num = 1
とかに戻しておきます。
Measure value size per multi-measure record : 2048
の検証
目的
ここでは「1 つのメジャーバリューに対して書き込めるデータサイズが 2048byte
」であることを確認します。
検証手順
最初に、42 行目 を下記のように変更して、バリュー値に 2048 桁の数字( 2048 Byte のデータ)が入るようにします。
MeasureValue = str(random.uniform(1, 90)) ↓ digit = 2048 # 桁数 MeasureValue = str(random.randrange(10**(digit-1),10**digit))
次に 47 行目を下記のように修正してデータ型を変更します。
'Type': 'DOUBLE' ↓ 'Type': 'VARCHAR'
正しく 2048 Byte のデータが書き込めました。
次に digit = 2049
に変えてエラーになることを確認します。実行すると下記のようなエラーが表示されました。
(見やすさのため改行を入れています)
botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the WriteRecords operation: 1 validation error detected: Value '790・・(中略)・・263' at 'records.1.member.measureValues.1.member.value' failed to satisfy constraint: Member must have length less than or equal to 2048
日本語データで確認してみる
Amazon Timestream のクォータのドキュメントには Measure value size per multi-measure record
の単位が記載されていません。そのためこの数字が、「メジャー値の文字数、桁数」なのか「Byte」なのかよく分かりません。
そのため、このクォータ値についてはもう少し検証を続けてみたいと思います。
次に、日本語をデータとして入れてみたいと思います。
( IoT のユースケースだと日本語のデータが入ることは無いかもしれませんが、センサーによっては「注意状態です」とか「対応が必要です」といった日本語情報を送ってくるものもあるかもしれません。が、実際にそういうデータを MQTT などでやり取りした経験はまだ筆者にはありません…)
一時的に gen_item()
の関数を下記のように変更します。8 行目 の str_num = 682
で 682 文字のランダムな「ひらがな」から成るデータを書き込みます。
AWS Cloudshell の文字コード は utf_8
なので 「ひらがな 682 文字で 2046 Byte( 1 文字 = 3Byte )」をバリュー値として書き込むことになります。
def gen_item(): records = [] MultiMeasureValue = [] start_item_num = 0 end_item_num = 1 for multi_measure_num in range(start_item_num,end_item_num): jp = ["あ","い","う","え","お","か","き","く","け","こ","さ","し","す","せ","そ","た","ち","つ","て","と","な","に","ぬ","ね","の","は","ひ","ふ","へ","ほ","ま","み","む","め","も","や","ゆ","よ","ら","り","る","れ","ろ","わ","を","ん"] str_num = 682 word = "" for i in range(str_num): word += jp[random.randint(0,len(jp)-1)] MeasureValue = str(word) print('MeasureValue :' + str(MeasureValue)) dummy_multi_measure = 'item_' + str(multi_measure_num) myitem = { 'Name': dummy_multi_measure, 'Value': MeasureValue, 'Type': 'VARCHAR' }, records.append(myitem) t = records[multi_measure_num - start_item_num] MultiMeasureValue.append(t[0]) return MultiMeasureValue
関数 gen_item()
を修正してスクリプトを実行できたらクエリを発行して確認してみます。
次に str_num = 683
に変更して再度スクリプトを実行します。なお、683 文字のひらがなは 2049 Byte です。
すると下記のようなエラーになりました。
(見やすさのため改行を入れています)
botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the WriteRecords operation: The measure name and value of the multi-measure record with measure name dummy_metrics exceeds the maximum supported length for measure names and values. See Quotas in the Timestream developer guide for additional information.
これまでの結果より、マルチメジャーレコードにおけるバリュー値の最大長は2048 Byte
であるようです。 (解釈に誤りがあるようでしたらご指摘いただけますと幸いです)
終わったら変更箇所をもとに戻しておきます。
Measures per table : 8192
の検証
Measures per table
はシングルメジャーレコード、マルチメジャーレコードの両方に係るクォータになります。
目的
ここでは「テーブルあたりの メジャーの数(メジャー名の種類)が 8192 個」であることを確認します。
検証手順
先程のスクリプトとあまり変わりませんが、少し修正した下記のコードで検証しました。
import boto3 from botocore.config import Config import time import random session = boto3.Session() write_client = session.client('timestream-write', region_name='us-east-1', config=Config(read_timeout=20, # リクエストタイムアウト(秒) max_pool_connections=5000, # 最大接続数 retries={'max_attempts': 10})) # 最大試行回数 DatabaseName='mytestdb' TableName='mytesttbl' def current_milli_time(): return round(time.time() * 1000) def gen_dimensions(): municipalities = random.choice(['Shinjuku', 'Toshima', 'Nakano', 'Ota', 'Chiyoda']) gateway_id = random.choice(['gateway_1', 'gateway_2', 'gateway_3']) device_name = str(municipalities) + '_' + str(gateway_id) dimensions = [ {'Name': 'Location', 'Value': 'Tokyo'}, {'Name': 'Municipalities', 'Value': municipalities}, {'Name': 'DeviceName', 'Value': device_name} ] return dimensions # 何件のメジャー値(アイテム、RDBのカラムに相当するもの)を作成するか # 1レコードあたりに入れるアイテムを複数作成する def gen_item(): records = [] MultiMeasureValue = [] start_item_num = 0 # 任意のアイテム番号からMeasureValueを作成 end_item_num = 1 # start_item_numから 256を超えない範囲で指定 for multi_measure_num in range(start_item_num,end_item_num): #ランダムな数値データをメジャーバリューとして作成 MeasureValue = str(random.uniform(1, 90)) dummy_multi_measure = 'item_' + str(multi_measure_num) myitem = { 'Name': dummy_multi_measure, 'Value': MeasureValue, 'Type': 'DOUBLE' }, records.append(myitem) t = records[multi_measure_num - start_item_num] MultiMeasureValue.append(t[0]) return MultiMeasureValue # レコードの生成 def gen_dummy_record(measure_name_num): records_X = [] # 何件のレコードを作成するか ; 100件:(0,100) 最大100 for record_num in range(0,1): dummy_metrics = 'dummy_metrics_' + str(measure_name_num) dummy_measure = { 'Dimensions': gen_dimensions(), 'MeasureName': dummy_metrics, 'MeasureValueType': 'MULTI', 'MeasureValues': gen_item(), 'Time': str(current_milli_time()), 'TimeUnit': 'MILLISECONDS' } records_X.append(dummy_measure) time.sleep(1/1000) return records_X for write_num in range(0,8191): # 生成したレコードを何回書き込むか print ("start write_records...: " + str(write_num)) result = write_client.write_records(DatabaseName=DatabaseName, TableName=TableName, Records=gen_dummy_record(write_num), CommonAttributes={})
急ごしらえのため少々効率が悪いですが、レコードを生成するタイミング(for write_num in range(0,8192)
の箇所)で、1 レコード毎にメジャー名(measure_name
)を変えて格納します。
これによりメジャー名が全て異なる 8192 行のレコードが書き込まれます。
(dummy_metrics_0
, dummy_metrics_1
, …, dummy_metrics_8191
の8192個)
書き込めたら下記のSQLで measure_name
が一意であるレコード数をカウントして、8192 種類のmeasure_name
が登録されていることを確認します。
SELECT count(DISTINCT measure_name) FROM "mytestdb"."mytesttbl"
格納されたレコードを確認すると、measure_name
の項目がdummy_metrics_8191
という 8192 個目のレコードが最後に登録されていることが分かります。
(dummy_metrics_0
〜dummy_metrics_8191
の8192 個)
先程のスクリプトで書き込まれたレコードは下記の通り 8192行 なので、measure_name
の項目が全てバラバラなレコードが 8192 行書き込まれたことが確認できました。
次に、スクリプトを下記のように変更して、8193 個目(8193種類目)のメジャー名(dummy_metrics_8192
)を持つレコードを 1 行書き込んでみます。
Records=gen_dummy_measure(write_num), CommonAttributes={}) ↓ Records=gen_dummy_measure(8192), CommonAttributes={})
for write_num in range(0,8192) ↓ for write_num in range(0,1)
スクリプトを実行すると下記のようなエラーが発生します。
(見やすさのため適宜開業しています。)
botocore.errorfactory.RejectedRecordsException: An error occurred (RejectedRecordsException) when calling the WriteRecords operation: One or more records have been rejected. See RejectedRecords for details.
次に、再度下記のようにコードを変更して、登録済みのメジャー名(dummy_metrics_8191
)を持つレコードを1行追加で書き込んでみます。
Records=gen_dummy_measure(write_num), CommonAttributes={}) ↓ Records=gen_dummy_measure(8191), CommonAttributes={})
登録できたらクエリを発行して、8193 個目のレコードとして追加できていることを確認します。
全レコード数が 8193 行となり、1レコード追加されたことも確認できました。
以上のことから、 「テーブルあたり登録できるメジャーの最大数(measure_name
の種類)は 8192 個」 であることが分かりました。
確認結果のまとめ
以上の検証で分かったことをまとめて表形式で図にしてみました。
(誤り等ありましたらご指摘いただけると幸いです)
最後に
マルチメジャーレコードを中心に Amazon Timestream のメジャーに関するクォータをいくつか確認してみました。
IoT のユースケースだと、無数のデバイスから多種多様なデータを扱いたい場合があると思います。これらのクォータを踏まえて最適な設計をしていきたいと思います。
以上です。